
#include <BOpenGL>
#include "ILineBlock.h"
#include "member_BTexture.h"

using namespace BWE;

ILineBlock::ILineBlock()
{
	visible = true;
	align = Align_Left;
	wordWrap = true;
	lineHeight = 0;
	lineSpacing = 0;

	dirty= true;
	index = 0;
	count = 0;
	rowHeight = 0;
}
ILineBlock::ILineBlock(const ILineBlock& other)
{
	visible = true;
	align = other.align;
	wordWrap = other.align;
	lineHeight = other.lineHeight;
	lineSpacing = other.lineSpacing;

	dirty = true;
	index = 0;
	count = 0;
	rowHeight = 0;
}
ILineBlock::~ILineBlock()
{

}

void ILineBlock::reset()
{
	cells.reset();
	for (auto it = text.begin(); it < text.end(); it++)
	{
		ICodeCell& cell = cells.append();
		cell.code = *it;
	}
	index = 0;
	count = 0;
	dirty = true;
	if (format)
		format->changed(this->text);
}
void ILineBlock::reset(const BString& text)
{
	this->text = text;
	reset();
}
void ILineBlock::prepend(const BCode& code)
{
	this->text.prepend(code);
	ICodeCell& cell = cells.prepend();
	cell.code = code;
	dirty = true;
	index++;
	if (format)
		format->changed(this->text);
}
void ILineBlock::append(const BString& text)
{
	this->text.append(text);
	for (auto it = text.begin(); it < text.end(); it++)
	{
		ICodeCell& cell = cells.append();
		cell.code = *it;
	}
	dirty = true;
	if (format)
		format->changed(this->text);
}
bool ILineBlock::remove(int pos, int size)
{
	if (!cells.remove(pos, size))
		return false;
	if (index > pos + size)
		index -= size;
	else if (index > pos)
		index = pos;
	if (index > cells.size())
		index = cells.size();
	if (count > cells.size() - index)
		count = cells.size() - index;
	dirty = true;
	flush();
	return true;
}
bool ILineBlock::insert(const BCode& code)
{
	if (!cells.insert(index, ICodeCell()))
		return false;
	ICodeCell& cell = cells[index];
	cell.code = code;
	index++;
	dirty = true;
	flush();
	return true;
}
bool ILineBlock::insert(const BString& text)
{
	int count = text.count();
	if (!cells.insert(index, ICodeCell(), count))
		return false;
	for (auto it = text.begin(); it.valid(); it++)
	{
		ICodeCell& cell = cells[index++];
		cell.code = *it;
	}
	dirty = true;
	flush();
	return true;
}
bool ILineBlock::insert(int pos, const BString& text)
{
	int count = text.count();
	if (index >= pos)
		index += count;
	if (!cells.insert(pos, ICodeCell(), count))
		return false;
	for (auto it = text.begin(); it.valid(); it++)
	{
		ICodeCell& cell = cells[pos++];
		cell.code = *it;
	}
	dirty = true;
	flush();
	return true;
}
void ILineBlock::flush()
{
	text.reset();
	for (int i = 0; i < cells.size(); i++)
		text.append(cells[i].code);
	if (format)
		format->changed(this->text);
	dirty = true;
}

void ILineBlock::collect(BString& text, int pos, int count) const
{
	if (count < 0)
	{
		pos += count;
		count = -count;
	}
	if (pos < 0 || pos >= cells.size())
		return;
	int end = pos + count;
	if (end > cells.size())
		end = cells.size();
	for (int i = pos; i < end; i++)
	{
		text.append(cells[i].code);
	}
}
void ILineBlock::collect(BString& text) const
{
	collect(text, index, count);
}

bool ILineBlock::selectWord()
{
	if (cells.empty())
		return false;

	if (!cells.check(index))
		return false;

	bool large = cells[index].code.isLarge();
	bool space = cells[index].code.isSpace();
	bool punct = cells[index].code.isPunct();
	bool alpha = cells[index].code.isAlpha();

	int begin = index;
	while (begin >= 0)
	{
		const BCode& code = cells[begin].code;
		if (large != code.isLarge())
			break;
		if (space != code.isSpace())
			break;
		if (punct != code.isPunct())
			break;
		if (alpha != code.isAlpha())
			break;
		begin--;
	}
	begin++;
	int end = begin;
	while (end < cells.size())
	{
		const BCode& code = cells[end].code;
		if (large != code.isLarge())
			break;
		if (space != code.isSpace())
			break;
		if (punct != code.isPunct())
			break;
		if (alpha != code.isAlpha())
			break;
		end++;
	}
	index = begin;
	count = end - begin;
	return count > 0;
}
bool ILineBlock::removeSelection()
{
	int pos = bMin(index, index + count);
	if (cells.check(pos) && count)
	{
		cells.remove(pos, abs(count));
		index = pos;
		count = 0;
		dirty = true;
		flush();
		return true;
	}
	return false;
}

bool ILineBlock::seekHead()
{
	if (index != 0 || count != 0)
	{
		index = 0;
		count = 0;
		return true;
	}
	return false;
}
bool ILineBlock::seekPoint(const BPoint& point)
{
	int index = indexAt(point);
	if (index != index || count != 0)
	{
		index = index;
		count = 0;
		return true;
	}
	return false;
}
bool ILineBlock::seekTail()
{
	int tail = cells.size();
	if (cells.last().code == '\n')
		tail = cells.size() - 1;
	if (index != tail || count != 0)
	{
		index = tail;
		count = 0;
		return true;
	}
	return false;
}

int ILineBlock::seekPrevCode()
{
	index--;
	count = 0;
	return index;
}
int ILineBlock::seekNextCode()
{
	index++;
	count = 0;
	return index;
}
int ILineBlock::seekPrevWord()
{
	count = 0;
	if (cells(index - 1).code.isPunct())
	{
		index--;
		return index;
	}
	if (cells(index - 1).code.isSpace())
	{
		while (index > 0)
		{
			if (!cells[index - 1].code.isSpace())
				break;
			index--;
		}
		return index;
	}
	if (cells(index - 1).code.isDigit())
	{
		while (index > 0)
		{
			if (!cells[index - 1].code.isDigit())
				break;
			index--;
		}
		return index;
	}
	while (index > 0)
	{
		if (cells[index - 1].code.isSpace())
			break;
		if (cells[index - 1].code.isDigit())
			break;
		if (cells[index - 1].code.isPunct())
			break;
		index--;
	}
	return index;
}
int ILineBlock::seekNextWord()
{
	int size = cells.size();
	if (index == size)
	{
		index++;
		return index;
	}
	if (cells[index].code.isPunct())
	{
		index++;
		return index;
	}
	if (cells[index].code.isSpace())
	{
		while (index < size)
		{
			if (!cells[index].code.isSpace())
				break;
			index++;
		}
		return index;
	}
	if (cells[index].code.isDigit())
	{
		while (index < size)
		{
			const BCode& code = cells[index].code;
			if (code < 255)
			{
				if (!code.isDigit())
					break;
			}
			index++;
		}
		return index;
	}
	while (index < size)
	{
		const BCode& code = cells[index].code;
		if (code < 255)
		{
			if (code.isSpace())
				break;
			if (code.isPunct())
				break;
			if (code.isDigit())
				break;
		}
		index++;
	}
	return index;
}

int ILineBlock::indexAt(const BPoint& pos) const
{
	if (pos.y() < rect.top())
		return -1;
	if (pos.y() > rect.bottom())
		return cells.size();
	BPoint point = pos - rect.pos();
	int index = 0;
	while (index < cells.size())
	{
		BRect rect = cells[index].rect;
		if (rect.bottom() < point.y())
		{
			index++;
			continue;
		}
		if (rect.top() > point.y())
			break;
		if (rect.center().x() > point.x())
			break;
		index++;
	}
	return index;
}
int ILineBlock::wordWidth(int pos, int& end) const
{
	int width = cells[pos].rect.width();
	const BCode& code = cells[pos].code;
	const char* ustr = code.str();
	if (code.isSpace())
		return width;
	if (code.isPunct())
		return width;
	if (code.num() > 1)
		return width;
	end = pos;
	while (++end < cells.size())
	{
		const BCode& code = cells[end].code;
		if (code.isSpace())
			break;
		if (code.isPunct())
			break;
		if (code.num() > 1)
			break;
		width += cells[end].rect.width();
	}
	return width;
}

void ILineBlock::adjust(int pageWidth, BTextFormat* textFormat)
{
	if (format)
		textFormat = format;
	if (!textFormat && !font)
		return;
	if (dirty)
	{
		rowHeight = lineHeight;
		const BFont* realFont = font;
		if (realFont && rowHeight < realFont->size())
			rowHeight = realFont->size();
		if (textFormat && rowHeight < textFormat->lineHeight(0))
			rowHeight = textFormat->lineHeight(0);

		for (int i = 0; i < cells.size(); i++)
		{
			ICodeCell& cell = cells[i];
			if (textFormat)
			{
				if (const BFont* font = textFormat->font(range.min() + i))
					realFont = font;
			}
			if (rowHeight < realFont->size())
				rowHeight = realFont->size();
			cell.fresh(realFont);
		}

		int width = 0;
		int up = (int)(rowHeight * 0.2f);
		int x = 0;
		int y = 0;
		int wordEnd = 0;
		for (int i = 0; i < cells.size(); i++)
		{
			ICodeCell& cell = cells[i];
			int codeWidth = cell.rect.width();
			if (codeWidth < 1)
				continue;
			if (pageWidth > 0)
			{
				if (codeWidth < pageWidth && x + codeWidth > pageWidth)
				{
					x = 0;
					y += rowHeight + lineSpacing;
				}
				else if (wordWrap && i > wordEnd && x > 0)
				{
					int ww = wordWidth(i, wordEnd);
					if (x + ww > pageWidth)
					{
						x = 0;
						y += rowHeight;
					}
				}
			}
			if (cell.code == '\t')
			{
				int times = x / codeWidth + 1;
				int width = times * codeWidth - x;
				cell.rect.setPos(x, y);
				cell.rect.setSize(width, rowHeight);
				cell.image_rect = cell.rect;
				x = times * codeWidth;
			}
			else
			{
				cell.rect.setPos(x, y);
				cell.rect.setHeight(rowHeight);
				cell.image_rect.setPos(x + cell.image_left, cell.rect.bottom() - cell.image_top - up);
				x += cell.rect.width();
			}
			if (width < x)
				width = x;
		}
		textSize.set(width, y + rowHeight);
		rect.setSize(width, y + rowHeight);
		if (pageWidth > width)
		{
			int xoffset = 0;
			if (align & Align_Right)
				xoffset = pageWidth - width;
			if (align & Align_HCenter)
				xoffset = (pageWidth - width) / 2;
			if (xoffset > 0)
			{
				for (int i = 0; i < cells.size(); i++)
				{
					ICodeCell& cell = cells[i];
					cell.rect.move(xoffset, 0);
					cell.image_rect.move(xoffset, 0);
				}
			}
		}
		dirty = false;
	}
}
void ILineBlock::paint(BPainter& painter, BTextFormat* textFormat)
{
	BColor textColor = painter.color();
	int bottom = painter.clip().bottom();
	BPoint pos = rect.pos();
	for (int i = 0; i < cells.size(); i++)
	{
		const ICodeCell& cell = cells[i];
		if (cell.code.utf() == 16)
		{
			painter.setColor(255, 0, 0, 128);
			painter.fillRect(cell.rect + pos);
		}
	}
	painter.setColor(textColor);
	painter.unbind();
	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	for (int i = 0; i < cells.size(); i++)
	{
		const ICodeCell& cell = cells[i];
		BRect rect = cell.image_rect + pos;
		if (rect.top() > bottom)
			break;
		if (cell.image.empty())
			continue;
		if (format)
		{
			const BColor* color = format->color(i);
			if (!color || !painter.setColor(*color))
				painter.setColor(textColor);
		}
		else if (textFormat)
		{
			const BColor* color = textFormat->color(range.min() + i);
			if (!color || !painter.setColor(*color))
				painter.setColor(textColor);
		}
		int bx = rect.x();
		int by = rect.y();
		int bw = rect.width();
		int bh = rect.height();
		if (texture_member(cell.image)->dirty)
			cell.image->flush();
		glBindTexture(GL_TEXTURE_2D, texture_member(cell.image)->tbo);
		glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f);		glVertex2i(bx, by);
		glTexCoord2f(0.0f, 1.0f);		glVertex2i(bx, by + bh);
		glTexCoord2f(1.0f, 1.0f);		glVertex2i(bx + bw, by + bh);
		glTexCoord2f(1.0f, 0.0f);		glVertex2i(bx + bw, by);
		glEnd();
		glBindTexture(GL_TEXTURE_2D, 0);
	}
	glDisable(GL_TEXTURE_2D);
}

